import { _decorator, Component, director, Node } from 'cc';
import { TileMapManager } from './TileMapManager';
import { createUINode } from '../../Utils';
import Levels, { ILevel } from '../../Levels';
import DataManager, { IRecord } from '../../Runtime/DataManager';
import EventManager from '../../Runtime/EventManager';
import { DIRECTION_ENUM, ENTITY_STATE_ENUM, ENTITY_TYPE_ENUM, EVENT_TYPE, SCENE_ENUM } from '../../Enums';
import { PlayerManager } from '../Player/PlayerManager';
import { TILE_HEIGHT, TILE_WIDTH } from './TileManager';
import { WoodenSkeletonManager } from '../WoodenSkeleton/WoodenSkeletonManager';
import { IconSkeletonManager } from '../IconSkeleton/IconSkeletonManager';
import { DoorManager } from '../Door/DoorManager';
import { BurstManager } from '../Burst/BurstManager';
import { SmokeManager } from '../Smoke/SmokeManager';
import FaderManager from '../../Runtime/FaderManager';
import { ShakeManager } from '../UI/ShakeManager';
import { SpikesManager } from '../Spikes/SpikesManager';
const { ccclass, property } = _decorator;

@ccclass('BattleManager')
export class BattleManager extends Component {
    level: ILevel;
    stage: Node;
    isInited: boolean = false;

    protected onLoad(): void {
        DataManager.Instance.levelIndex = 1;

        EventManager.Instance.on(EVENT_TYPE.NEXT_LEVEL, this.nextLevel, this);
        EventManager.Instance.on(EVENT_TYPE.SHOW_SMOKE, this.generateSmoke, this);
        EventManager.Instance.on(EVENT_TYPE.PLAYER_MOVE_END, this.checkArrived, this);
        EventManager.Instance.on(EVENT_TYPE.RECORD_STEP, this.record, this);
        EventManager.Instance.on(EVENT_TYPE.REVOKE_STEP, this.revoke, this);
        EventManager.Instance.on(EVENT_TYPE.RESTART_LEVEL, this.initLevel, this);
        EventManager.Instance.on(EVENT_TYPE.OUT_BATTLE, this.outBattle, this);
    }

    protected onDestroy(): void {
        EventManager.Instance.off(EVENT_TYPE.NEXT_LEVEL, this.nextLevel);
        EventManager.Instance.off(EVENT_TYPE.SHOW_SMOKE, this.generateSmoke);
        EventManager.Instance.off(EVENT_TYPE.PLAYER_MOVE_END, this.checkArrived);
        EventManager.Instance.off(EVENT_TYPE.RECORD_STEP, this.record);
        EventManager.Instance.off(EVENT_TYPE.REVOKE_STEP, this.revoke);
        EventManager.Instance.off(EVENT_TYPE.RESTART_LEVEL, this.initLevel);
        EventManager.Instance.off(EVENT_TYPE.OUT_BATTLE, this.outBattle);
    }

    start() {
        this.generateStage();
        this.initLevel();
    }

    async initLevel() {
        const level = Levels[`Level${DataManager.Instance.levelIndex}`];

        if (level) {
            if (this.isInited) {
                await FaderManager.Instance.fadeIn();
            } else {
                await FaderManager.Instance.mask();
            }

            this.clearLevel();

            this.level = level;

            DataManager.Instance.mapInfo = this.level.mapInfo;
            DataManager.Instance.mapRowCount = this.level.mapInfo.length;
            DataManager.Instance.mapColumnCount = this.level.mapInfo[0].length;

            await Promise.all([
                this.generateTileMap(),
                this.generateEnemies(),
                this.generateDoor(),
                this.generateBursts(),
                this.generateSpikes(),
                this.generatePlayer(),
            ]);
            await FaderManager.Instance.fadeOut();
            this.isInited = true;
        } else {
            this.outBattle();
        }
    }

    async outBattle() {
        await FaderManager.Instance.fadeIn();
        this.node.destroy();
        director.loadScene(SCENE_ENUM.Start);
    }

    nextLevel() {
        DataManager.Instance.levelIndex += 1;

        this.initLevel();
    }

    prevLevel() {
        DataManager.Instance.levelIndex -= 1;

        this.initLevel();
    }

    clearLevel() {
        this.stage.destroyAllChildren();
        DataManager.Instance.reset();
    }

    generateStage() {
        const stage = createUINode();
        this.stage = stage;
        stage.setParent(this.node);
        this.stage.addComponent(ShakeManager);
    }

    async generateTileMap() {
        const tileMap = new Node();
        tileMap.setParent(this.stage);

        const tileMapManager = tileMap.addComponent(TileMapManager);
        await tileMapManager.init();
        this.adaptMap();
    }

    async generatePlayer() {
        const player = createUINode();
        player.setParent(this.stage);
        const playerManager = player.addComponent(PlayerManager);
        await playerManager.init(this.level.player);
        DataManager.Instance.player = playerManager;
        EventManager.Instance.emit(EVENT_TYPE.PLAYER_BORNED, true);
    }

    async generateSmoke(x: number, y: number, direction: DIRECTION_ENUM) {
        // TODO 烟雾的优化
        const smoke = createUINode();
        smoke.setParent(this.stage);
        const smokeManager = smoke.addComponent(SmokeManager);
        await smokeManager.init({
            x,
            y,
            type: ENTITY_TYPE_ENUM.SMOKE,
            direction,
            state: ENTITY_STATE_ENUM.IDLE,
        });
    }

    async generateEnemies() {
        const enemies = this.level.enemies;
        const promises = [];
        for (let i = 0; i < enemies.length; i += 1) {
            const enemyParams = enemies[i];
            const enemy = createUINode();
            enemy.setParent(this.stage);
            const Manager = enemies[i].type === ENTITY_TYPE_ENUM.ENEMY_WOODEN ? WoodenSkeletonManager : IconSkeletonManager;
            const enemyManager = enemy.addComponent(Manager);
            promises.push(enemyManager.init(enemyParams));
            DataManager.Instance.enemies.push(enemyManager);
        }
        await Promise.all(promises);
    }

    async generateDoor() {
        const doorParams = this.level.door;
        const door = createUINode();
        door.setParent(this.stage);
        const doorManager = door.addComponent(DoorManager);
        await doorManager.init(doorParams);
        DataManager.Instance.door = doorManager;
    }

    async generateBursts() {
        const bursts = this.level.bursts;
        const promises = [];
        for (let i = 0; i < bursts.length; i += 1) {
            const burst = createUINode();
            burst.setParent(this.stage);
            const burstManager = burst.addComponent(BurstManager);
            promises.push(burstManager.init(bursts[i]));
            DataManager.Instance.bursts.push(burstManager);
        }
        await Promise.all(promises);
    }

    async generateSpikes() {
        const spikes = this.level.spikes;
        const promises = [];
        for (let i = 0; i < spikes.length; i += 1) {
            const burst = createUINode();
            burst.setParent(this.stage);
            const spikesManager = burst.addComponent(SpikesManager);
            promises.push(spikesManager.init(spikes[i]));
            DataManager.Instance.spikes.push(spikesManager);
        }
        await Promise.all(promises);
    }

    adaptMap() {
        const { mapRowCount, mapColumnCount } = DataManager.Instance;
        const x = TILE_WIDTH * mapRowCount / 2;
        const y = TILE_HEIGHT * mapColumnCount / 2 + 90;
        this.stage.setPosition(-x, y);
    }

    checkArrived() {
        const { x: playerX, y: playerY } = DataManager.Instance.player;
        const { x: doorX, y: doorY, state: doorState } = DataManager.Instance.door;
        if (playerX === doorX && playerY === doorY && doorState === ENTITY_STATE_ENUM.DEATH) {
            EventManager.Instance.emit(EVENT_TYPE.NEXT_LEVEL);
        }
    }

    record() {
        const item: IRecord = {
            player: {
                x: DataManager.Instance.player.targetX,
                y: DataManager.Instance.player.targetY,
                direction: DataManager.Instance.player.direction,
                state: DataManager.Instance.player.state === ENTITY_STATE_ENUM.IDLE || DataManager.Instance.player.state === ENTITY_STATE_ENUM.DEATH || DataManager.Instance.player.state === ENTITY_STATE_ENUM.AIR_DEATH ? DataManager.Instance.player.state : ENTITY_STATE_ENUM.IDLE,
                type: DataManager.Instance.player.type,
            },
            door: {
                x: DataManager.Instance.door.x,
                y: DataManager.Instance.door.y,
                direction: DataManager.Instance.door.direction,
                state: DataManager.Instance.door.state,
                type: DataManager.Instance.door.type,
            },
            enemies: DataManager.Instance.enemies.map(item => ({
                x: item.x,
                y: item.y,
                direction: item.direction,
                state: item.state,
                type: item.type,
            })),
            bursts: DataManager.Instance.bursts.map(item => ({
                x: item.x,
                y: item.y,
                direction: item.direction,
                state: item.state,
                type: item.type,
            })),
            spikes: DataManager.Instance.spikes.map(item => ({
                x: item.x,
                y: item.y,
                count: item.count,
                type: item.type,
            })),
        };
        DataManager.Instance.records.push(item);
    }

    revoke() {
        const item = DataManager.Instance.records.pop();
        if (item) {
            DataManager.Instance.player.x = DataManager.Instance.player.targetX = item.player.x;
            DataManager.Instance.player.y = DataManager.Instance.player.targetY = item.player.y;
            DataManager.Instance.player.state = item.player.state;
            DataManager.Instance.player.direction = item.player.direction;

            DataManager.Instance.door.x = item.door.x;
            DataManager.Instance.door.y = item.door.y;
            DataManager.Instance.door.state = item.door.state;
            DataManager.Instance.door.direction = item.door.direction;

            for (let i = 0; i < DataManager.Instance.enemies.length; i += 1) {
                const theItem = DataManager.Instance.enemies[i];
                theItem.x = item.enemies[i].x;
                theItem.y = item.enemies[i].y;
                theItem.state = item.enemies[i].state;
                theItem.direction = item.enemies[i].direction;
            }

            for (let i = 0; i < DataManager.Instance.bursts.length; i += 1) {
                const theItem = DataManager.Instance.bursts[i];
                theItem.x = item.bursts[i].x;
                theItem.y = item.bursts[i].y;
                theItem.state = item.bursts[i].state;
                theItem.direction = item.bursts[i].direction;
            }

            for (let i = 0; i < DataManager.Instance.spikes.length; i += 1) {
                const theItem = DataManager.Instance.spikes[i];
                theItem.x = item.spikes[i].x;
                theItem.y = item.spikes[i].y;
                theItem.count = item.spikes[i].count;
                theItem.type = item.spikes[i].type;
            }
        }
    }
}

